package com.yami.shop.security.api.controller;

import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.enums.SendType;
import com.yami.shop.bean.event.NewcomerRewardsEvent;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.constants.OauthCacheNames;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PrincipalUtil;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.config.WxConfig;
import com.yami.shop.dao.UserMapper;
import com.yami.shop.security.api.dto.MaCodeAuthenticationDTO;
import com.yami.shop.security.api.dto.SocialAuthenticationDTO;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.dao.AppConnectMapper;
import com.yami.shop.security.common.dto.AuthenticationDTO;
import com.yami.shop.security.common.enums.PortTypeEnum;
import com.yami.shop.security.common.enums.SocialType;
import com.yami.shop.security.common.enums.SysTypeEnum;
import com.yami.shop.security.common.manager.PasswordCheckManager;
import com.yami.shop.security.common.manager.PasswordManager;
import com.yami.shop.security.common.manager.TokenStore;
import com.yami.shop.security.common.model.AppConnect;
import com.yami.shop.security.common.service.AppConnectService;
import com.yami.shop.security.common.vo.TokenInfoVO;
import com.yami.shop.service.SmsLogService;
import com.yami.shop.service.UserCancelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author FrozenWatermelon
 * @date 2020/6/30
 */
@RestController
@Tag(name = "登录")
public class LoginController {

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AppConnectService appConnectService;
    @Autowired
    private UserCancelService userCancelService;

    @Autowired
    private PasswordCheckManager passwordCheckManager;

    @Autowired
    private SmsLogService smsLogService;

    @Autowired
    private PasswordManager passwordManager;

    @Autowired
    private AppConnectMapper appConnectMapper;

    @Autowired
    private WxConfig wxConfig;
    @Autowired
    private ApplicationContext applicationContext;

    @PostMapping("/login")
    @Operation(summary =  "账号密码登录(用于h5、pc登录)", description = "通过账号/手机号/用户名密码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> login(
            @Valid @RequestBody AuthenticationDTO authenticationDTO) {
        if(Objects.isNull(authenticationDTO.getPortType())){
            authenticationDTO.setPortType(PortTypeEnum.DTC.value());
        }
        String mobileOrUserName = authenticationDTO.getUserName();
        User user = getUser(mobileOrUserName);
        String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());

        // 半小时内密码输入错误十次，已限制登录30分钟
        passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY,user.getUserMobile(),decryptPassword, user.getLoginPassword());

        return ServerResponseEntity.success(getTokenInfoVO(user, null,authenticationDTO.getPortType()));
    }

    @PostMapping("/wx/login")
    @Operation(summary =  "账号密码登录(用于微信小程序和公众号)", description = "微信小程序和公众号通过账号/手机号/用户名密码登录, 二合一登录（包含绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> wxLogin(
            @Valid @RequestBody SocialAuthenticationDTO authenticationDTO) {
        if (StrUtil.isBlank(authenticationDTO.getTempUid())) {
            return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("tempUid is blank"));
        }
        if(Objects.isNull(authenticationDTO.getPortType())){
            authenticationDTO.setPortType(PortTypeEnum.DTC.value());
        }
        User user = getUser(authenticationDTO.getUserName());
        String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());
        // 半小时内密码输入错误十次，已限制登录30分钟
        passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY,user.getUserMobile(),decryptPassword, user.getLoginPassword());

        AppConnect appConnect = checkAndGetAppConnect(user.getUserId(), authenticationDTO.getSocialType(), authenticationDTO.getTempUid());

        return ServerResponseEntity.success(getTokenInfoVO(user, appConnect,authenticationDTO.getPortType()));
    }


    @PostMapping("/ma/login")
    @Operation(summary =  "微信小程序一键登录", description = "三合一登录（包含注册 + 绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> maLogin(@Valid @RequestBody MaCodeAuthenticationDTO maCodeAuthenticationDTO) {
        WxMaPhoneNumberInfo newPhoneNoInfo;
        if(Objects.isNull(maCodeAuthenticationDTO.getPortType())){
            return ServerResponseEntity.showFailMsg("参数异常");
        }
        try {
            newPhoneNoInfo = wxConfig.getWxMaService().getUserService().getNewPhoneNoInfo(maCodeAuthenticationDTO.getCode());
        } catch (WxErrorException e) {
            throw new YamiShopBindException(e.getMessage());
        }
        // 没有区号的手机号，国外手机号会有区号
        String mobile = newPhoneNoInfo.getPurePhoneNumber();

        return threeInOneLogin(mobile, maCodeAuthenticationDTO.getTempUid(), SocialType.MA.value(),maCodeAuthenticationDTO.getPortType());
    }


    @PostMapping("/smsLogin")
    @Operation(summary =  "手机号短信验证码登录", description = "三合一登录（包含注册 + 绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> smsLogin(
            @Valid @RequestBody SocialAuthenticationDTO authenticationDTO) {

        if (StrUtil.isBlank(authenticationDTO.getTempUid())
                && (Objects.equals(authenticationDTO.getSocialType(), SocialType.MA.value()) || Objects.equals(authenticationDTO.getSocialType(), SocialType.MP.value()))) {
            return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("tempUid is blank"));
        }
        if(Objects.isNull(authenticationDTO.getPortType())){
            authenticationDTO.setPortType(PortTypeEnum.DTC.value());
//            return ServerResponseEntity.showFailMsg("参数异常");
        }
        // 验证码登录
        boolean validCode;
        try {
            validCode = smsLogService.checkValidCode(authenticationDTO.getUserName(), String.valueOf(authenticationDTO.getPassWord()), SendType.LOGIN);
        }catch (YamiShopBindException e){
            // 验证码校验过频繁，请稍后再试
            return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("yami.user.code.check.too.much"));
        }
        if (!validCode){
            // 验证码有误或已过期
            return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("yami.user.code.error"));
        }
        // 如果没有注册的话，短信登录将会进行注册
        // 在pc/小程序/公众号的登录，都有短信登录的方法。但是公众号/小程序的短信登录，登录了之后会见这个用户和公众号/小程序绑定一起（登录并绑定）
        return threeInOneLogin(authenticationDTO.getUserName(), authenticationDTO.getTempUid(), authenticationDTO.getSocialType(),authenticationDTO.getPortType());
    }


    private TokenInfoVO getTokenInfoVO(User user, AppConnect appConnect,Integer portType) {
        // 删除token
        delToken(user.getUserId(), portType);
        UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
        userInfoInToken.setUserId(user.getUserId());
        userInfoInToken.setXsUserId(user.getXsUserId());
        userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
        userInfoInToken.setPortType(portType);
        userInfoInToken.setEnabled(user.getStatus() == 1);
        if (appConnect !=null) {
            userInfoInToken.setBizUid(appConnect.getBizUnionid());
            userInfoInToken.setBizUserId(appConnect.getBizUserId());
            userInfoInToken.setSessionKey(appConnect.getBizTempSession());
            userInfoInToken.setSocialType(appConnect.getAppId());
        }
        // 存储token返回vo
        return tokenStore.storeAndGetVo(userInfoInToken);
    }
    /**
     * 三合一登录（包含注册 + 绑定 + 登录）
     */
    private ServerResponseEntity<TokenInfoVO> threeInOneLogin(String mobile,String tempUid, Integer socialType,Integer portType) {
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobile));
        AppConnect appConnect = null;

        // 没有就注册
        if (user == null) {
            // 判断注销时间
            Boolean conformToRegister = userCancelService.isConformToRegister(mobile, "");
            if(!conformToRegister){
                return ServerResponseEntity.showFailMsg("注销180天内无法注册");
            }
            // if (Objects.equals(socialType,SocialType.MA.value()) || Objects.equals(socialType,SocialType.MP.value())) {
            //     appConnect = appConnectService.getByTempUid(tempUid);
            //     if (appConnect == null || appConnect.getUserId() != null) {
            //         return ServerResponseEntity.fail(ResponseEnum.TEMP_UID_ERROR);
            //     }
            // }
            // 注册并绑定用户
            user = appConnectService.registerAndBindUser(mobile, null, tempUid);

            // 免费活动发放
//            applicationContext.publishEvent(new NewcomerRewardsEvent(user.getUserId(),"7",null,"1"));
        } else  if(user.getStatus()==0){
            // 判断注销时间
            Boolean conformToRegister = userCancelService.isConformToRegister(mobile, "");
            if(!conformToRegister){
                return ServerResponseEntity.showFailMsg("注销180天内无法注册");
            }
            if (Objects.equals(socialType,SocialType.MA.value()) || Objects.equals(socialType,SocialType.MP.value())) {
                appConnect = appConnectService.getByTempUid(tempUid);
                if (appConnect == null || appConnect.getUserId() != null) {
                    return ServerResponseEntity.fail(ResponseEnum.TEMP_UID_ERROR);
                }
            }
            //保留注销的userid ，新注册的放在后面
            user.setUserMobile(user.getUserMobile()+"_cp");
            user.setUserName(user.getUserName()+"_cp");
            if(!Objects.isNull(user.getIdNumber())){
                user.setRealName(user.getRealName()+"_cp");
                user.setIdNumber(user.getIdNumber()+"1");
            }
            userMapper.updateById(user);
            // 注册并绑定用户
            user = appConnectService.registerAndBindUser(mobile, null, tempUid);
            // 免费活动发放
//            applicationContext.publishEvent(new NewcomerRewardsEvent(user.getUserId(),"7",null,"1"));
        }
        // else {
        //     // 绑定
        //     appConnect = checkAndGetAppConnect(user.getUserId(), socialType, tempUid);
        // }
        // 登录
        return ServerResponseEntity.success(getTokenInfoVO(user, appConnect,portType));
    }

    /**
     * 登录并绑定用户
     */
    private AppConnect checkAndGetAppConnect(String userId, Integer socialType,String tempUid) {
        // 只有微信公众号和小程序的登录会进行登录并绑定的操作
        if (!Objects.equals(socialType,SocialType.MA.value()) && !Objects.equals(socialType,SocialType.MP.value())) {
            return null;
        }
        // 在SocialLoginController 当中，会返回一个tempUid用来换取openid的
        AppConnect appConnect = appConnectService.getByTempUid(tempUid);
        if (appConnect == null) {
            throw new YamiShopBindException(ResponseEnum.TEMP_UID_ERROR);
        }
        // 二合一登录，啥意思呢？
        // 1. 绑定：将该账号和该微信的openId进行绑定
        // 2. 登录：返回token登录成功
        if (appConnect.getUserId() == null) {
            appConnectMapper.bindUserIdByTempUid(userId, appConnect.getTempUid());
        }
        // 如果有userId就判断下是不是一样的user，不是的话，不能够绑定
        else if (!Objects.equals(appConnect.getUserId(), userId)) {
            throw new YamiShopBindException(ResponseEnum.SOCIAL_ACCOUNT_BIND_BY_OTHER);
        }
        return appConnect;
    }

    private User getUser(String mobileOrUserName) {
        User user = null;
        // 手机验证码登陆，或传过来的账号很像手机号
        if (PrincipalUtil.isMobile(mobileOrUserName)) {
            user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobileOrUserName));
        }
        // 如果不是手机验证码登陆， 找不到手机号就找用户名
        if  (PrincipalUtil.isUserName(mobileOrUserName)) {
            user = userMapper.selectOneByUserName(mobileOrUserName);
        }
        // 如果不是手机验证码登陆， 找不到手机号就找用户名
        if  (user == null) {
            user = userMapper.selectOneByUserName(mobileOrUserName);
        }
        if (user == null) {
            throw new YamiShopBindException("yami.user.account.error");
        }
        return user;
    }

    private void delToken(String uid,Integer portType) {
        String uidToAccessKeyStr = OauthCacheNames.UID_TO_ACCESS + "0" + StrUtil.COLON + PortTypeEnum.getKey(portType)+ StrUtil.COLON + uid;
        Long size = RedisUtil.size(uidToAccessKeyStr);
        List<String> pop = RedisUtil.STRING_REDIS_TEMPLATE.opsForSet().pop(uidToAccessKeyStr, size);
        for (String s : pop) {
            String[] split = s.split(StrUtil.COLON);
            String accessKeyStr = OauthCacheNames.ACCESS + split[0];
            String refreshKeyStr = OauthCacheNames.REFRESH_TO_ACCESS + split[1];
            RedisUtil.del(accessKeyStr);
            RedisUtil.del(refreshKeyStr);
        }
    }


    @PostMapping("/loginByTokenAndPortType")
    @Operation(summary = "通过Token登录其他端口", description = "登录")
    public ServerResponseEntity<TokenInfoVO> loginByTokenAndPortType(@Valid @RequestBody Map<String,Object>  map) {
        if(Objects.isNull(map.get("authorization"))||Objects.isNull(map.get("portType"))){
            return ServerResponseEntity.showFailMsg("参数不全");
        }
        String accessToken = String.valueOf(map.get("authorization"));
        Integer portType = Integer.valueOf(map.get("portType").toString());
        // 当前登录的用户信息
        UserInfoInTokenBO userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true);
        userInfoInToken.setPortType(portType);
        // 删除token
        delToken(userInfoInToken.getUserId(), portType);
        return ServerResponseEntity.success(tokenStore.storeAndGetVo(userInfoInToken));
    }
}
